#include "LL1Parser.hpp"
#include "LRAnalyzer.hpp"
#include "LRTable.hpp"
#include "LRTableBuilder.hpp"
#include "TokenBlock.hpp"
#include "util.hpp"
#include <cstdio>
#include <cstdlib>
#include <queue>
#include <stack>
#include <string>
#include <vector>

struct AttributeItem {
  int status;
  std::string symbol;
  int val;
};

void printStack(std::stack<AttributeItem> stk1) {
  printf("stack: ");
  std::stack<AttributeItem> stk2;
  while (stk1.size() > 0) {
    stk2.push(stk1.top());
    stk1.pop();
  }
  while (stk2.size() > 0) {
    auto item = stk2.top();
    printf("{%d %s %d}", item.status, item.symbol.c_str(), item.val);
    stk2.pop();
  }
  printf("\n");
}

const auto endItem = AttributeItem{0, END_SYMNOL, 0};

class SAttributeCalculator {
public:
  std::vector<bool (*)(std::stack<AttributeItem> &, const LRTable &)>
      segmentFunc;
  LRTable table;

  int parse(const std::vector<std::string> &symbolVec) {
    std::queue<std::string> restSymbol;
    for (const auto &item : symbolVec) {
      restSymbol.push(item);
    }
    restSymbol.push(END_SYMNOL);
    std::stack<AttributeItem> stk;
    stk.push(endItem);

    while (stk.size() > 0) {
      printStack(stk);
      auto status = stk.top().status;
      std::string nextSymbol = is_all_digits(restSymbol.front().c_str())
                                   ? "num"
                                   : restSymbol.front();
      auto tableItem = table.query(status, nextSymbol);
      if (tableItem.isReduction) {
        if (segmentFunc[tableItem.num](stk, table)) {
          break;
        }
      } else {
        auto sym = restSymbol.front();
        restSymbol.pop();
        auto attItem = AttributeItem{tableItem.num, sym, atoi(sym.c_str())};
        stk.push(attItem);
      }
    }

    return stk.top().val;
  }
};

std::vector<std::string> getSymbols(FILE *fp) {
  std::vector<std::string> res;
  TokenBlock *p = get_tokens(fp);
  for (TokenBlock *q = p; q != nullptr; q = q->nextBlock) {
    res.push_back(q->token);
  }
  free_TokenBlock(p);
  return res;
}

bool func0(std::stack<AttributeItem> &stk, const LRTable &tab) {
  auto item = stk.top();
  stk.pop();
  auto sym = "Res";
  auto val = item.val;
  // auto status = tab.query(stk.top().status, sym);
  auto attItem = AttributeItem{-1, sym, val};
  stk.push(attItem);
  return true;
}

bool func1(std::stack<AttributeItem> &stk, const LRTable &tab) {
  auto rItem = stk.top();
  stk.pop();
  stk.pop();
  auto lItem = stk.top();
  stk.pop();
  auto sym = "Expr";
  auto val = lItem.val + rItem.val;
  auto status = tab.query(stk.top().status, sym);
  auto attItem = AttributeItem{status.num, sym, val};
  stk.push(attItem);
  return false;
}

bool func2(std::stack<AttributeItem> &stk, const LRTable &tab) {
  auto rItem = stk.top();
  stk.pop();
  stk.pop();
  auto lItem = stk.top();
  stk.pop();
  auto sym = "Expr";
  auto val = lItem.val - rItem.val;
  auto status = tab.query(stk.top().status, sym);
  auto attItem = AttributeItem{status.num, sym, val};
  stk.push(attItem);
  return false;
}

bool func3(std::stack<AttributeItem> &stk, const LRTable &tab) {
  auto item = stk.top();
  stk.pop();
  auto sym = "Expr";
  auto val = item.val;
  auto status = tab.query(stk.top().status, sym);
  auto attItem = AttributeItem{status.num, sym, val};
  stk.push(attItem);
  return false;
}

bool func4(std::stack<AttributeItem> &stk, const LRTable &tab) {
  auto rItem = stk.top();
  stk.pop();
  stk.pop();
  auto lItem = stk.top();
  stk.pop();
  auto sym = "Term";
  auto val = lItem.val * rItem.val;
  auto status = tab.query(stk.top().status, sym);
  auto attItem = AttributeItem{status.num, sym, val};
  stk.push(attItem);
  return false;
}

bool func5(std::stack<AttributeItem> &stk, const LRTable &tab) {
  auto rItem = stk.top();
  stk.pop();
  stk.pop();
  auto lItem = stk.top();
  stk.pop();
  auto sym = "Term";
  auto val = lItem.val / rItem.val;
  auto status = tab.query(stk.top().status, sym);
  auto attItem = AttributeItem{status.num, sym, val};
  stk.push(attItem);
  return false;
}

bool func6(std::stack<AttributeItem> &stk, const LRTable &tab) {
  auto item = stk.top();
  stk.pop();
  auto sym = "Term";
  auto val = item.val;
  auto status = tab.query(stk.top().status, sym);
  auto attItem = AttributeItem{status.num, sym, val};
  stk.push(attItem);
  return false;
}

bool func7(std::stack<AttributeItem> &stk, const LRTable &tab) {
  auto item = stk.top();
  stk.pop();
  auto sym = "Factor";
  auto val = item.val;
  auto status = tab.query(stk.top().status, sym);
  auto attItem = AttributeItem{status.num, sym, val};
  stk.push(attItem);
  return false;
}

bool func8(std::stack<AttributeItem> &stk, const LRTable &tab) {
  stk.pop();
  auto item = stk.top();
  stk.pop();
  stk.pop();
  auto sym = "Factor";
  auto val = item.val;
  auto status = tab.query(stk.top().status, sym);
  auto attItem = AttributeItem{status.num, sym, val};
  stk.push(attItem);
  return false;
}

SAttributeCalculator init() {
  auto analyzer = LRAnalyzer();
  analyzer.defineExpresses = {
      {"Res", {"Expr"}},
      {"Expr", {"Expr", "+", "Term"}}, // Expr.val=Term.val+Expr.val
      {"Expr", {"Expr", "-", "Term"}}, // Expr.val=Term.val-Expr.val
      {"Expr", {"Term"}},

      {"Term", {"Term", "*", "Factor"}}, // Term.val=Factor.val*Term.val
      {"Term", {"Term", "/", "Factor"}}, // Term.val=Factor.val/Term.val
      {"Term", {"Factor"}},              // Term.val=Factor.val

      {"Factor", {"num"}},            // Factor.val=num.val
      {"Factor", {"(", "Expr", ")"}}, // Factor.val=Expr.val
  };
  analyzer.terminals = {"+", "-", "*", "/", "(", ")", "num", ""};
  auto pjSetVec = analyzer.getProjectSets();
  auto tableBuilder = LRTableBuilder();

  auto followSet = tableBuilder.getFollowSet(
      DefineExpr2define(analyzer.defineExpresses), analyzer.terminals);
  auto table = tableBuilder.buildTableFrom(pjSetVec, analyzer.defineExpresses,
                                           followSet);
  SAttributeCalculator res;
  res.table = table;
  res.segmentFunc = {func0, func1, func2, func3, func4,
                     func5, func6, func7, func8};
  return res;
}

int main(int argc, char *argv[]) {
  if (argc < 2) {
    return 0;
  }
  FILE *input_fp = fopen(argv[1], "r");
  if (input_fp == nullptr) {
    printf("failed to open %s\n", argv[1]);
    return 0;
  }
  auto symbolVec = getSymbols(input_fp);
  fclose(input_fp);

  auto calculator = init();
  int res = calculator.parse(symbolVec);
  printf("%d\n", res);

  return 0;
}
